"""
    jupylet/node.py
    
    Copyright (c) 2020, Nir Aides - nir@winpdb.org

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice, this
       list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice,
       this list of conditions and the following disclaimer in the documentation
       and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""


import copy
import math
import glm

from .utils import glm_dumps, glm_loads


class Object(object):
    
    """实现一个跟踪其属性更改的对象。

    它用于实现只在必要时进行昂贵计算的惰性机制。
    """

    def __init__(self, dirty=True):
        
        self._items = {}
        self._dirty = set([True]) if dirty else set()

    def __dir__(self):
        return list(self._items.keys()) + super().__dir__()
    
    def __getattr__(self, k):
        
        if k not in self._items:
            return super().__getattribute__(k)
            
        return self._items[k]
    
    def __setattr__(self, k, v):
        
        if k == '_items' or k not in self._items:
            return super().__setattr__(k, v)
        
        self._items[k] = v
        self._dirty.add(k)

    def __repr__(self):    
        return '%s(%s)' % (type(self).__name__, ', '.join(
            '%s=%s' % i for i in list(self.__dict__.items()) + list(self._items.items()) if i[0][0] != '_'
        ))


def q2aa(rotation, deg=False):
    """将四元数转换为角度(angle)+轴(axis)。"""
    
    if not rotation or rotation == (1., 0., 0., 0.):
        return 0, glm.vec3(0, 0, 1)
    
    c, xs, ys, zs = rotation #glm.conjugate(rotation)

    angle = math.acos(c) * 2
    s = math.sin(angle / 2)

    if s == 0:
        return 0, glm.vec3(0, 0, 1)

    if deg:
        angle = round(180 * angle / math.pi, 3)
    
    return angle, glm.vec3(xs / s, ys / s, zs / s)


def aa2q(angle, axis=glm.vec3(0, 0, 1)):
    return glm.angleAxis(angle, glm.normalize(axis))


_i4 = glm.mat4(1.)


class Node(Object):
  
    """用矩阵变换处理和表示几何运算。
    
    将局部和全局3D坐标中的缩放、旋转和平移处理，并表示为单个矩阵（延迟维护）变换。

    Args:
        name (str, optional): 对象名称。
        anchor (glm.vec3, optional): 
    """

    def __init__(
        self, 
        name='', 
        anchor=None,
        scale=None, 
        rotation=None, 
        position=None,
    ):
        
        super().__init__()
        
        self.name = name

        self.anchor = glm.vec3(anchor) if anchor else glm.vec3(0.)
        self.scale0 = glm.vec3(scale) if scale else glm.vec3(1.)
        self.rotation = glm.quat(rotation) if rotation else glm.quat(1., 0., 0., 0.)
        self.position = glm.vec3(position) if position else glm.vec3(0.)

        self._itemz = None

        self._matrix = glm.mat4(1.)

    @property
    def scale(self):
        return self.scale0

    @scale.setter
    def scale(self, value):
        self.scale0 = value

    @property
    def matrix(self):
        
        if self._itemz != [self.anchor, self.scale0, self.rotation, self.position]:
            self._itemz = copy.deepcopy([
                self.anchor, 
                self.scale0, 
                self.rotation, 
                self.position
            ])

            t0 = glm.translate(_i4, self.position)
            r0 = t0 * glm.mat4_cast(self.rotation)
            s0 = glm.scale(r0, self.scale0)
            a0 = glm.translate(s0, -self.anchor)

            self._matrix = a0

            self._dirty.add('_matrix')

        return self._matrix

    def move_local(self, xyz):
        """按局部坐标系中给定的位移移动。

        Args:
            xyz (glm.vec3): 置换(矩阵)。
        """
        rxyz = glm.mat4_cast(self.rotation) * glm.vec4(xyz, 1.)
        self.position += rxyz.xyz
        
    def rotate_local(self, angle, axis=(0., 0., 1.)):
        """在局部坐标系中，围绕给定轴逆时针旋转给定角度。

        Args:
            angle (float): 以弧度为单位的角度。
            axis (glm.vec3): 旋转轴。
        """
        self.rotation *= aa2q(angle, glm.vec3(axis))
            
    def move_global(self, xyz):
        """按全局坐标系中给定的位移移动。

        Args:
            xyz (glm.vec3): 置换(矩阵)。
        """
        self.position += xyz
        
    def rotate_global(self, angle, axis=(0., 0., 1.)):
        """在全局坐标系中，围绕给定轴逆时针旋转给定角度。

        Args:
            angle (float): 以弧度为单位的角度。
            axis (glm.vec3): 旋转轴。
        """
        self.rotation = aa2q(angle, glm.vec3(axis)) * self.rotation
            
    @property
    def up(self):
        """glm.vec3: 返回局部向上( up (+y) )坐标值。"""
        return (self.matrix * glm.vec4(0, 1, 0, 0)).xyz

    @property
    def front(self):
        """glm.vec3: 返回局部向前(front (+z) )坐标值。"""
        return (self.matrix * glm.vec4(0, 0, 1, 0)).xyz

    def get_state(self):
        """获取定义对象状态的属性字典。
        
        Returns:
            dict: 存储属性的字典对象。
        """
        return dict(
            rotation = glm_dumps(glm.quat(self.rotation)), 
            position = glm_dumps(glm.vec3(self.position)),
            anchor = glm_dumps(glm.vec3(self.anchor)), 
            scale0 = glm_dumps(glm.vec3(self.scale0)), 
        )

    def set_state(self, s):
        """根据指定的属性字典设置对象状态。
        
        Args:
            s (dict): 以前通过调用 ``get_state()`` 返回的属性字典。
        """
        for k, v in s.items():
            setattr(self, k, glm_loads(v))

